Type Declaration
Ambient Declarations
In TypeScript, ambient declarations are a way to tell the compiler about the existence and shape of code that’s defined outside TypeScript — like in JavaScript libraries, global variables, or external APIs.
They live inside .d.ts files (declaration files), and do not generate JavaScript output.
They’re purely for type checking and editor IntelliSense.
Think of them as “type definitions without implementation”.
Why Do We Need .d.ts Files?
- TypeScript needs type information, but plain JavaScript libraries don’t provide it.
.d.tsfiles bridge the gap: they describe the types of values, functions, and classes that come from elsewhere.- Popular JS libraries (React, Express, Lodash, etc.) ship with
.d.tsfiles, or you can install them via DefinitelyTyped (npm install @types/react).
Syntax of Ambient Declarations
They often use the declare keyword. Examples:
declare var→ for global variablesdeclare function→ for global functionsdeclare class→ for global classesdeclare module→ for modulesdeclare namespace→ for grouping related declarations
Declaring a Global Variable
Suppose a JavaScript file adds a global variable:
// script.js
window.myGlobal = "Hello world!";
In TypeScript, we can declare it:
// globals.d.ts
declare var myGlobal: string;
Declaring a Module
If you’re using a JavaScript library that doesn’t have types, you can declare a module:
// lodash.d.ts
declare module "lodash" {
export function chunk<T>(array: T[], size?: number): T[][];
}
Now in your TypeScript code:
import { chunk } from "lodash";
let result = chunk([1, 2, 3, 4], 2); // [[1, 2], [3, 4]]
Even though Lodash is written in JS, you get type safety + autocomplete.
Declaring a Namespace
// myLib.d.ts
declare namespace MyLib {
function greet(name: string): void;
let version: string;
}
Usage:
MyLib.greet("Alice");
console.log(MyLib.version);
Merging with Existing Types
You can augment existing modules:
// express-custom.d.ts
import "express";
declare module "express" {
interface Request {
user?: { id: number; name: string };
}
}
Now in Express code:
app.use((req, res, next) => {
req.user = { id: 1, name: "Alice" }; // ✅ recognized
next();
});
Key Rules of .d.ts Files
- They don’t generate JS output — only types.
- They must use
declarefor globals, modules, etc. - You can place them:
- in your project (
src/types/or@types/) - or install from DefinitelyTyped (
@types/*packages).
- TypeScript automatically picks up
.d.tsfiles if they’re in your project ornode_modules/@types.
DefinitelyTyped
- DefinitelyTyped is a huge open-source repository that contains type declaration files (
.d.ts) for popular JavaScript libraries. - Since many JS libraries don’t ship with TypeScript support, the community maintains these declaration files separately.
- You can install them via npm as
@types/<library-name>.
Example:
- React itself is plain JS (historically).
- To use React in TypeScript, you install:
npm install react react-dom
npm install --save-dev @types/react @types/react-dom - Now your TS project knows the types of React components, hooks, etc.
How It Works
- Install a JS library (
npm install lodash). - Install its type declarations (
npm install --save-dev @types/lodash). - TypeScript automatically finds the
.d.tsfile innode_modules/@types. - You get type safety + IntelliSense without writing your own declarations.
Using Lodash with @types/lodash
Without types, TypeScript doesn’t know what _.chunk does:
import _ from "lodash";
let result = _.chunk([1, 2, 3, 4], 2);
console.log(result);
If @types/lodash is installed:
- TypeScript knows
chunk<T>(array: T[], size?: number): T[][]. resultis correctly inferred asnumber[][].- If you misuse it:
_.chunk(123, 2); // ❌ Error: Argument of type 'number' is not assignable to parameter of type 'any[]'
TypeScript catches the error at compile time.
Using Express with @types/express
npm install express
npm install --save-dev @types/express
Now in TypeScript:
import express, { Request, Response } from "express";
const app = express();
app.get("/", (req: Request, res: Response) => {
res.send("Hello World");
});
RequestandResponsecome from@types/express.- You get full IntelliSense for Express APIs.
Declaring types for external libraries
This is needed when you use a JavaScript library with no built-in types and no @types/... package. In such cases, you must declare the types yourself so TypeScript knows how to handle the library.
Why Do We Need to Declare Types?
- TypeScript only understands types it has definitions for.
- Many modern libraries include their own
.d.tsfiles ✅ - Others rely on DefinitelyTyped (
@types/...) ✅ - But sometimes: ❌ no built-in types, ❌ no
@typesavailable → you must declare types manually.
Ways to Declare Types for External Libraries
- Quick and dirty: declare the module as
any. - Create a
.d.tsfile with basic type definitions. - Write a full declaration file (
index.d.ts) with detailed typings. - Publish to DefinitelyTyped if it’s useful for the community.
Declaring a Module as any
Suppose you import a JS library called cool-lib, but TS has no idea about it:
import cool from "cool-lib"; // ❌ Error: Cannot find module 'cool-lib'
Fix with a quick .d.ts:
// cool-lib.d.ts
declare module "cool-lib";
Now TypeScript stops complaining, but everything is any:
cool.doSomething(); // no IntelliSense, no type safety
Useful as a temporary fix, but not recommended long term.
Declaring a Module with Specific Functions
Suppose cool-lib exports a function greet(name: string): string.
// cool-lib.d.ts
declare module "cool-lib" {
export function greet(name: string): string;
}
Now in your TS code:
import { greet } from "cool-lib";
console.log(greet("Alice")); // ✅ typed as string
TypeScript knows greet takes a string and returns a string.
If you misuse it:
greet(123); // ❌ Error: Argument of type 'number' is not assignable to parameter of type 'string'